package ferp.core.calc;

import ferp.core.calc.scorer.PassingRostov;
import ferp.core.game.Game;
import ferp.core.game.Settings;
import ferp.core.player.Player;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * User: olegs
 * Date: 9/9/12 10:05 PM
 */
public class Rostov extends Sochi
{
  public Rostov()
  {
    // initialize Rostov's costs
    costs[0] = new int[] { 2,  2,  2, 1};
    costs[1] = new int[] { 4,  4,  4, 2};
    costs[2] = new int[] { 6,  6,  6, 3};
    costs[3] = new int[] { 8,  8,  8, 4};
    costs[4] = new int[] {10, 10, 10, 5};
  }

  @Override
  public boolean closed(Game g, Settings settings, Player p)
  {
    return super.closed(g, settings, p) && g.getScore(p.id()).mountain.current == 0;
  }

  @Override
  public void passing(Game g, Settings settings)
  {
    PassingRostov.instance.score(g, settings, this);
  }

  @Override
  public int getMissedTrickCost(int contractTricks) {return HELP_FACTOR;}

  @Override
  public void writeToPool(Game g, Settings settings, Player p, long value)
  {
    boolean poolsAreClosed = super.closed(g, settings, g.player(0)) && super.closed(g, settings, g.player(1)) && super.closed(g, settings, g.player(2));

    // all players have closed their pools
    if (poolsAreClosed)
      // reduce from mountain and "help" to opponents if needed (without "podlyot")
      reduceFromMountain(g, settings, p, value, false);
    else
    {
      // get half cost of the declared game or passing
      long half = value / 2;

      // add to pool and "help" if needed (with "podlyot")
      addToPool(g, settings, p, half);

      // reduce from mountain and "help" to opponents if needed (with "podlyot")
      reduceFromMountain(g, settings, p, half, true);
    }
  }

  private void addToPool(Game g, Settings settings, Player p, long value)
  {
    if (g.getScore(p.id()).pool.current + value <= settings.poolSize)
      g.getScore(p.id()).pool.add(value);
    else
    {
      // get remaining amount for "help"
      long help = g.getScore(p.id()).pool.current + value - settings.poolSize;

      // close the pool
      if (g.getScore(p.id()).pool.current != settings.poolSize)
        g.getScore(p.id()).pool.add(settings.poolSize - g.getScore(p.id()).pool.current);

      // get opponents sorted by Rostov's "help" rules
      List<Player> opponents = getOpponentsSortedByHelpOrder(g, settings, p);
      for (Player op : opponents)
      {
        // "help" the specific opponent to close his pool
        long min = Math.min(help, settings.poolSize - g.getScore(op.id()).pool.current);

        if (min > 0)
        {
          g.getScore(op.id()).pool.add(min);
          writeToWhists(g, p, op, HELP_FACTOR * min);

          help -= min;
        }
      }

      // the player still has some amount to "help" the opponents
      if (help > 0)
        complete(g, settings, p, help, true);
    }
  }

  private void reduceFromMountain(Game g, Settings settings, Player p, long value, boolean punishByAddingToMountain)
  {
    if (g.getScore(p.id()).mountain.current >= value)
      g.getScore(p.id()).mountain.add(-value);
    else
    {
      // get remaining amount for "help"
      long help = value - g.getScore(p.id()).mountain.current;

      // eliminate mountain
      if (g.getScore(p.id()).mountain.current > 0)
        g.getScore(p.id()).mountain.add(-g.getScore(p.id()).mountain.current);

      // get opponents sorted by Rostov's "help" rules
      List<Player> opponents = getOpponentsSortedByHelpOrder(g, settings, p);

      // "help" the opponents
      for (Player op : opponents)
      {
        // "help" the specific opponent to eliminate his mountain
        long min = Math.min(help, g.getScore(op.id()).mountain.current);

        if (min > 0)
        {
          g.getScore(op.id()).mountain.add(-min);
          writeToWhists(g, p, op, HELP_FACTOR * min);

          help -= min;
        }
      }

      // the player still has some amount to "help" the opponents
      if (help > 0)
        complete(g, settings, p, help, punishByAddingToMountain);
    }
  }

  private void complete(Game g, Settings settings, Player p, long help, boolean punishByAddingToMountain)
  {
    Player p2 = g.next(p);
    Player p3 = g.next(p2);
    boolean closed2 = closed(g, settings, p2);
    boolean closed3 = closed(g, settings, p3);
    long hh = help / 2;
    long hw = HELP_FACTOR / 2 * help;

    // first check if opponents are totally closed, if true then just write whists on them
    if (closed2 && closed3)
    {
      // s: write only half wists on each opponent
      writeToWhists(g, p, p2, hw);
      writeToWhists(g, p, p3, hw);
    }
    else
    {
      // "podlyot"
      if (punishByAddingToMountain)
      {
        // s: write half whists if closed, mountain otherwise
        if (closed2)
          writeToWhists(g, p, p2, hw);
        else
          writeToMountain(g, p2, hh);

        if (closed3)
          writeToWhists(g, p, p3, hw);
        else
          writeToMountain(g, p3, hh);
      }
      else
      {
        // "help" to each opponent by reducing the his mountain and writing whists on him
        if (g.getScore(p2.id()).mountain.current > 0)
        {
          long min = Math.min(g.getScore(p2.id()).mountain.current, help);

          writeToMountain(g, p2, -min);
          writeToWhists(g, p, p2, HELP_FACTOR * min);

          help -= min;
        }

        if (help > 0 && g.getScore(p3.id()).mountain.current > 0)
        {
          long min = Math.min(g.getScore(p3.id()).mountain.current, help);

          writeToMountain(g, p3, -min);
          writeToWhists(g, p, p3, HELP_FACTOR * min);

          help -= min;
        }

        // the player still has some amount to "help" the opponents => write whists on both opponents
        if (help > 0)
        {
          // s: half whists
          writeToWhists(g, p, p2, hw);
          writeToWhists(g, p, p3, hw);
        }
      }
    }
  }

  private List<Player> getOpponentsSortedByHelpOrder(Game g, Settings settings, Player helper)
  {
    List<Player> opponents = new LinkedList<Player>();
    Player p2 = g.next(helper);
    Player p3 = g.next(p2);

    // if p2 not closed add it to the list
    if (!closed(g, settings, p2))
      opponents.add(p2);

    // if p3 not closed add it to the list
    if (!closed(g, settings, p3))
      opponents.add(p3);

    // if number of opponents to help is less than 2 - return the list
    if (opponents.size() < 2)
      return opponents;

    // sort opponents using Rostov's "help" rules (if pools and mountains are equal then opponents ordered clockwise)
    if (g.getScore(p2.id()).pool.current > g.getScore(p3.id()).pool.current ||
       (g.getScore(p2.id()).pool.current == g.getScore(p3.id()).pool.current && g.getScore(p2.id()).mountain.current <= g.getScore(p3.id()).mountain.current))
      return opponents;

    // otherwise p3 is the first opponent to "help"
    Collections.reverse(opponents);

    return opponents;
  }

  // help factor for writing whists on opponent
  private final static int HELP_FACTOR = 10;

/*
  public static void main(String[] argv)
  {
    Settings settings = new Settings();
    settings.gameFlavor = Settings.GameFlavor.ROSTOV;
    Game g = new Game(settings);

    settings.poolSize = 20;

    g.getScore(0).mountain.add(-9);
    g.getScore(0).pool.add(20);
    g.getScore(0).whists[1].add(902);
    g.getScore(0).whists[2].add(426);

    g.getScore(1).mountain.add(16);
    g.getScore(1).pool.add(20);

    g.getScore(2).mountain.add(67);
    g.getScore(2).pool.add(20);

    g.player.declarer = Game.NO_ID;

    g.bid = Bid.pass();

    g.setTricks(0, 0);
    g.setTricks(1, 3);
    g.setTricks(2, 7);

    Calculator.get(Settings.GameFlavor.ROSTOV).passing(g, settings, 0);

    for (int i = 0; i < Game.PLAYERS; ++i)
      System.out.println(g.getScore(i));
  }
*/
}
